#include <os/fat.h>
#include <os/fatconfig.h>
#include <os/fatunicode.h>

#if FS_USE_LFN // only configuration LFN this module is enable

// OEM<==>Unicode coversions from static code page
// SBCS fixed code page
#if FS_CODE_PAGE != 0 && FS_CODE_PAGE < 900
uint16_t FsUni2Oem(uint32_t unicode, uint16_t code)
{
    uint16_t ch = 0;
    uint16_t *p = (uint16_t *)CVTBL(uc, FS_CODE_PAGE);

    if (unicode < 0x80) // ASICII
        ch = unicode;
    else // No-ASCII
    {
        if (unicode < 0x10000 && code == FS_CODE_PAGE) // is valid code page
        {
            for (ch = 0; ch < 0x80 && unicode != p[ch]; ch++) // is in map table
                ;
            ch = (ch + 0x80) & 0xff;
        }
    }
    return ch;
}

uint16_t FsOem2Uni(uint16_t oem, uint16_t code)
{
    uint16_t c;
    uint16_t *p = (uint16_t *)CVTBL(uc, FS_CODE_PAGE);

    if (oem < 0x80) // ASCII
    {
        c = oem;
    }
    else // extened char
    {
        if (code == FS_CODE_PAGE)
        {
            if (oem < 0x100)
                c = p[oem - 0x80];
        }
    }
    return c;
}
#endif

#if FS_CODE_PAGE >= 900
uint16_t FsUni2Oem(uint32_t unicode, uint16_t code)
{
    uint16_t c, uc;
    uint16_t *p;
    int i, n, li, hi;

    if (unicode < 0x80) // ASCII
        c = (uint16_t)unicode;
    else
    {
        if (unicode < 0x10000 && code == FS_CODE_PAGE)
        {
            uc = (uint16_t)unicode;
            p = CVTBL(uni2oem, FS_CODE_PAGE);
            hi = sizeof(CVTBL(uni2oem, FS_CODE_PAGE)) / 4 - 1;
            li = 0;
            for (n = 16; n; n--)
            {
                i = li + (hi - li) / 2;
                if (uc == p[i * 2])
                    break;
                if (uc > p[i * 2])
                    li = i;
                else
                    hi = i;
            }
            if (n != 0)
                c = p[i * 2 + 1];
        }
    }
    return c;
}

uint16_t FsOem2Uni(uint16_t oem, uint16_t code)
{
    uint16_t c;
    uint16_t *p;
    int i, n, li, hi;

    if (oem < 0x80)
        c = oem;
    else
    {
        if (code == FS_CODE_PAGE)
        {
            p = CVTBL(oem2uni, FS_CODE_PAGE);
            hi = sizeof(CVTBL(oem2uni, FS_CODE_PAGE)) / 4 - 1;
            li = 0;

            for (n = 16; n; n--) // find OEM
            {
                i = li + (hi - li) / 2;
                if (oem == p[i * 2])
                    break;
                if (oem > p[i * 2])
                    li = i;
                else
                    hi = i;
            }
            if (n)
                c = p[i * 2 + 1];
        }
    }
    return c;
}

#endif

#if FS_CODE_PAGE == 0
static const uint16_t code_page[] = {437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 0};
static const uint16_t *const code_table[] = {uc437, uc720, uc737, uc771, uc775, uc850, uc852, uc855, uc857, uc860, uc861, uc862, uc863, uc864, uc865, uc866, uc869, 0};

uint16_t FsUni2Oem(uint32_t uincode, uint16_t code)
{
    const uint16_t *p;
    uint16_t c = 0, uc;
    int i, n, li, hi;

    if (unicode < 0x80) // ASCII
        c = (uint16_t)unicode;
    else
    { // NO-ASCII
        if (unicode < 0x10000)
        {
            uc = (uint16_t)uni;
            if (code < 900) // SBCS
            {
                // get conversation table
                for (i = 0; code_page[i] != 0 && code_page[i] != code; i++)
                    ;
                p = code_table[i];
                if (p)
                {
                    for (c = 0; c < 0x80 && uc != p[c]; c++)
                    {
                        c = (c + 0x80) & 0xFF;
                    }
                }
            }
            else // DBSC
            {
                switch (code) // coversation table
                {
                case 932:
                    p = uni2oem932;
                    hi = sizeof(uni2oem932) / 4 - 1;
                    break;
                case 936:
                    p = uni2oem936;
                    hi = sizeof(uni2oem936) / 4 - 1;
                    break;
                case 949:
                    p = uni2oem949;
                    hi = sizeof(uni2oem949) / 4 - 1;
                    break;
                case 950;
                    p = uni2oem950;
                    hi = sizeof(uni2oem950) / 4 - 1;
                    break;
                    default:
                    break;
                }
                if (p) // is valid code page
                {
                    li = 0;
                    for (n = 16; n; n--)
                    {
                        i = li + (hi - li) / 2;
                        if (uc == p[i * 2])
                            break;
                        if (uc > p[i * 2])
                            li = 1;
                        if (uc < p[i * 2])
                            hi = i;
                    }
                    if (n)
                        c = p[i * 2 + 1];
                }
            }
        }
    }
    return c;
}

uint16_t FsOem2Uni(uint16_t oem, uint16_t code)
{
    uint16_t *p;
    uint16_t c;
    int i, n, li, hi;

    if (oem < 0x80) // ASCII
        c = oem;
    else
    { // Extended char
        p = 0;
        if (code < 900) // SBCS
        {
            for (i = 0; code_table[i] && code_table[i] != code; i++)
                ; // get code table
            if (p)
            {
                if (oem < 0x100) // Is a valid code page
                    c = p[oem - 0x80];
            }
        }
        else // DBCS
        {
            switch (code)
            {
            case 932:
                p = oem2uni932;
                hi = sizeof(oem2uni932) / 4 - 1;
                break;
            case 936： p = oem2uni936;
                hi = sizeof(oem2uni936) / 4 - 1;
                break;
                case 949:
                p = oem2uni949;
                hi = sizeof(oem2uni949) / 4 - 1;
                break;
            case 950:
                p = oem2uni950;
                hi = sizeof(oem2uni950) / 4 - 1;
                break;
            default:
                break;
            }
            if (p)
            {
                li = 0;
                for (n = 16; n; n--)
                {
                    i = li + (hi - li) / 2;
                    if (oem == p[i * 2])
                        break;
                    if (oem < p[i * 2])
                        li = i;
                    else
                        hi = i;
                }
                if (n)
                    c = p[i * 2 + 1];
            }
        }
    }
    return c;
}
#endif

uint32_t FsWtoupper(uint32_t unicode)
{
    uint16_t *p;
    uint16_t uc, bc, nc, cmd;
    static const uint16_t cvt1[] = {
        /* Compressed up conversion table for U+0000 - U+0FFF */
        /* Basic Latin */
        0x0061, 0x031A,
        /* Latin-1 Supplement */
        0x00E0, 0x0317,
        0x00F8, 0x0307,
        0x00FF, 0x0001, 0x0178,
        /* Latin Extended-A */
        0x0100, 0x0130,
        0x0132, 0x0106,
        0x0139, 0x0110,
        0x014A, 0x012E,
        0x0179, 0x0106,
        /* Latin Extended-B */
        0x0180, 0x004D, 0x0243, 0x0181, 0x0182, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, 0x0187, 0x0189, 0x018A, 0x018B, 0x018B, 0x018D, 0x018E, 0x018F, 0x0190, 0x0191, 0x0191, 0x0193, 0x0194, 0x01F6, 0x0196, 0x0197, 0x0198, 0x0198, 0x023D, 0x019B, 0x019C, 0x019D, 0x0220, 0x019F, 0x01A0, 0x01A0, 0x01A2, 0x01A2, 0x01A4, 0x01A4, 0x01A6, 0x01A7, 0x01A7, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AC, 0x01AE, 0x01AF, 0x01AF, 0x01B1, 0x01B2, 0x01B3, 0x01B3, 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01B8, 0x01BA, 0x01BB, 0x01BC, 0x01BC, 0x01BE, 0x01F7, 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C4, 0x01C5, 0x01C4, 0x01C7, 0x01C8, 0x01C7, 0x01CA, 0x01CB, 0x01CA,
        0x01CD, 0x0110,
        0x01DD, 0x0001, 0x018E,
        0x01DE, 0x0112,
        0x01F3, 0x0003, 0x01F1, 0x01F4, 0x01F4,
        0x01F8, 0x0128,
        0x0222, 0x0112,
        0x023A, 0x0009, 0x2C65, 0x023B, 0x023B, 0x023D, 0x2C66, 0x023F, 0x0240, 0x0241, 0x0241,
        0x0246, 0x010A,
        /* IPA Extensions */
        0x0253, 0x0040, 0x0181, 0x0186, 0x0255, 0x0189, 0x018A, 0x0258, 0x018F, 0x025A, 0x0190, 0x025C, 0x025D, 0x025E, 0x025F, 0x0193, 0x0261, 0x0262, 0x0194, 0x0264, 0x0265, 0x0266, 0x0267, 0x0197, 0x0196, 0x026A, 0x2C62, 0x026C, 0x026D, 0x026E, 0x019C, 0x0270, 0x0271, 0x019D, 0x0273, 0x0274, 0x019F, 0x0276, 0x0277, 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x2C64, 0x027E, 0x027F, 0x01A6, 0x0281, 0x0282, 0x01A9, 0x0284, 0x0285, 0x0286, 0x0287, 0x01AE, 0x0244, 0x01B1, 0x01B2, 0x0245, 0x028D, 0x028E, 0x028F, 0x0290, 0x0291, 0x01B7,
        /* Greek, Coptic */
        0x037B, 0x0003, 0x03FD, 0x03FE, 0x03FF,
        0x03AC, 0x0004, 0x0386, 0x0388, 0x0389, 0x038A,
        0x03B1, 0x0311,
        0x03C2, 0x0002, 0x03A3, 0x03A3,
        0x03C4, 0x0308,
        0x03CC, 0x0003, 0x038C, 0x038E, 0x038F,
        0x03D8, 0x0118,
        0x03F2, 0x000A, 0x03F9, 0x03F3, 0x03F4, 0x03F5, 0x03F6, 0x03F7, 0x03F7, 0x03F9, 0x03FA, 0x03FA,
        /* Cyrillic */
        0x0430, 0x0320,
        0x0450, 0x0710,
        0x0460, 0x0122,
        0x048A, 0x0136,
        0x04C1, 0x010E,
        0x04CF, 0x0001, 0x04C0,
        0x04D0, 0x0144,
        /* Armenian */
        0x0561, 0x0426,

        0x0000 /* EOT */
    };
    static const uint16_t cvt2[] = {
        /* Compressed up conversion table for U+1000 - U+FFFF */
        /* Phonetic Extensions */
        0x1D7D, 0x0001, 0x2C63,
        /* Latin Extended Additional */
        0x1E00, 0x0196,
        0x1EA0, 0x015A,
        /* Greek Extended */
        0x1F00, 0x0608,
        0x1F10, 0x0606,
        0x1F20, 0x0608,
        0x1F30, 0x0608,
        0x1F40, 0x0606,
        0x1F51, 0x0007, 0x1F59, 0x1F52, 0x1F5B, 0x1F54, 0x1F5D, 0x1F56, 0x1F5F,
        0x1F60, 0x0608,
        0x1F70, 0x000E, 0x1FBA, 0x1FBB, 0x1FC8, 0x1FC9, 0x1FCA, 0x1FCB, 0x1FDA, 0x1FDB, 0x1FF8, 0x1FF9, 0x1FEA, 0x1FEB, 0x1FFA, 0x1FFB,
        0x1F80, 0x0608,
        0x1F90, 0x0608,
        0x1FA0, 0x0608,
        0x1FB0, 0x0004, 0x1FB8, 0x1FB9, 0x1FB2, 0x1FBC,
        0x1FCC, 0x0001, 0x1FC3,
        0x1FD0, 0x0602,
        0x1FE0, 0x0602,
        0x1FE5, 0x0001, 0x1FEC,
        0x1FF3, 0x0001, 0x1FFC,
        /* Letterlike Symbols */
        0x214E, 0x0001, 0x2132,
        /* Number forms */
        0x2170, 0x0210,
        0x2184, 0x0001, 0x2183,
        /* Enclosed Alphanumerics */
        0x24D0, 0x051A,
        0x2C30, 0x042F,
        /* Latin Extended-C */
        0x2C60, 0x0102,
        0x2C67, 0x0106, 0x2C75, 0x0102,
        /* Coptic */
        0x2C80, 0x0164,
        /* Georgian Supplement */
        0x2D00, 0x0826,
        /* Full-width */
        0xFF41, 0x031A,

        0x0000 /* EOT */
    };

    if (unicode < 0x10000) // Is in BMP
    {
        uc = (uint16_t)unicode;
        p = uc < 0x1000 ? (uint16_t *)cvt1 : (uint16_t *)cvt2;
        while (1)
        {
            bc = *p++;          // get blocked base
            if (!bc || uc < bc) // no match
                break;
            nc = *p++; // get processing command
            cmd = nc >> 8;
            nc &= 0xff;
            if (uc < bc + nc) // is the block
            {
                switch (cmd)
                {
                case 0:
                    uc = p[uc - bc]; // table conversion
                    break;
                case 1:
                    uc -= (uc - bc); // case pair
                    break;
                case 2:
                    uc -= 16; // shift-16
                    break;
                case 3:
                    uc -= 32; // shift-32
                    break;
                case 4:
                    uc -= 48; // shift-48
                    break;
                case 5:
                    uc -= 26; // shift-26
                    break;
                case 6:
                    uc += 8; // shift+8
                    break;
                case 7:
                    uc -= 80; // shift-80
                    break;
                case 8:
                    uc -= 0x1c60; // shift-0x1C60
                    break;
                default:
                    break;
                }
                if (!cmd)
                    p += nc; // skip table if need
            }
        }
        unicode = uc;
    }
    return unicode;
}
#endif
